package bootsxt;

import jakarta.annotation.PreDestroy;
import jakarta.servlet.http.HttpServletResponse;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.videoio.VideoCapture;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

@SpringBootApplication
public class VideoStreamApplication {

    public static void main(String[] args) {
        // 提取和加载本地库文件
        extractLibrary();
        System.setProperty("USE_NATIVE_IMAGE_JAVA_PLATFORM_MODULE_SYSTEM", "false");
        SpringApplication.run(VideoStreamApplication.class, args);
    }

    private static void extractLibrary() {
        try {
            // 设置本地库路径
            System.setProperty("java.library.path", new File("./", "opencv_java4100x64.dll").getAbsolutePath());
            // 加载 OpenCV 本地库
            System.load(new File("./", "opencv_java4100x64.dll").getAbsolutePath());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("加载 OpenCV 本地库失败", e);
        }
    }
}

@RestController
class VideoStreamController {

    private static final int BUFFER_SIZE = 10 * 1024 * 1024; // 最大缓冲区大小：10MB
    private static final int MAX_FRAME_QUEUE_SIZE = 100;  // 最多缓存的帧数
    private final BlockingQueue<byte[]> frameBuffer = new ArrayBlockingQueue<>(MAX_FRAME_QUEUE_SIZE);
    private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);  // 定时任务线程池
    private final AtomicInteger activeClients = new AtomicInteger(0); // 当前活跃客户端数量

    private VideoCapture camera;  // 用于捕获视频帧

    // 用于精确管理客户端会话
    private final Object lock = new Object();
    private final ConcurrentMap<Long, ClientSession> activeSessions = new ConcurrentHashMap<>();  // 存储当前活跃的客户端会话

    public VideoStreamController() {
        // 每秒打印当前活跃客户端数量
        executorService.scheduleAtFixedRate(this::logActiveClientCount, 1, 1, TimeUnit.SECONDS);
    }

    /**
     * 打印当前活跃客户端数量，用于调试和监控
     */
    private synchronized void logActiveClientCount() {
        System.out.println("当前活跃客户端数量: " + activeClients.get());
        if (activeClients.get() <= 0){
            cleanUp();
        }
    }

    /**
     * 视频流接口，返回实时的视频数据流
     * @param response HttpServletResponse，用于发送 HTTP 响应
     * @throws IOException 当 I/O 操作失败时抛出异常
     */
    @GetMapping(value = "/stream", produces = "multipart/x-mixed-replace; boundary=frame")
    public void streamVideo(HttpServletResponse response, @RequestParam(defaultValue = "high") String quality) throws IOException {
        synchronized (lock) {
            // 客户端每连接一次，活跃客户端数量 +1
            activeClients.incrementAndGet();

            // 如果摄像头未打开，则打开摄像头
            if (camera == null || !camera.isOpened()) {
                initializeCamera();
            }
        }

        response.setContentType("multipart/x-mixed-replace; boundary=frame");
        OutputStream out = response.getOutputStream();
        Mat frame = new Mat();

        long sessionId = System.nanoTime();  // 使用当前时间戳作为会话 ID
        ClientSession clientSession = new ClientSession(sessionId, out);

        // 将当前客户端会话加入到活跃会话列表
        activeSessions.put(sessionId, clientSession);

        // 每个客户端启动一个心跳任务，周期性地检查连接状态
        executorService.scheduleAtFixedRate(() -> checkClientHeartbeat(clientSession), 0, 5, TimeUnit.SECONDS);

        // 持续流式读取视频帧并发送给客户端
        while (camera.read(frame)) {
            if (frame.empty()) {
                continue; // 跳过空帧
            }
            // 根据请求的清晰度调整帧的分辨率
            // 将 OpenCV Mat 类型的帧转换为字节数组（JPEG 编码）
            byte[] frameData = encodeFrame(adjustFrameResolution(frame, quality));
            // 将新帧加入到帧缓冲队列
            manageFrameBuffer(frameData);

            // 将帧写入到 HTTP 响应流
            writeFrameToResponse(out);
        }

        // 客户端断开后，更新客户端数量
        synchronized (lock) {
            // 仅当客户端在活动列表中时才减少活跃客户端计数，避免重复递减
            if (activeSessions.containsKey(sessionId)) {
                activeClients.decrementAndGet();
            }
        }

        // 清理掉当前的客户端会话
        activeSessions.remove(sessionId);
    }

    private void initializeCamera() {
        try {
            if (camera != null) {
                camera.release(); // 释放之前的摄像头资源
            }
            camera = new VideoCapture(0); // 初始化新的摄像头对象
            if (!camera.isOpened()) {
                throw new IllegalStateException("摄像头初始化失败，无法访问摄像头。");
            }
            System.out.println("摄像头初始化成功");

            // 设置摄像头参数，尝试解决低端摄像头兼容性问题
            boolean setupSuccessful = tryCameraSettings(camera, 640, 480);
            if (!setupSuccessful) {
                System.err.println("低端摄像头，切换到更低配置...");
                setupSuccessful = tryCameraSettings(camera, 320, 240); // 降低配置再试
            }
            if (!setupSuccessful) {
                throw new IllegalStateException("摄像头初始化失败，尝试其他分辨率或格式仍失败！");
            }
        } catch (Exception e) {
            System.err.println("摄像头初始化异常: " + e.getMessage());
        }
    }

    private boolean tryCameraSettings(VideoCapture capture, int width, int height) {
        capture.set(org.opencv.videoio.Videoio.CAP_PROP_FRAME_WIDTH, width);
        capture.set(org.opencv.videoio.Videoio.CAP_PROP_FRAME_HEIGHT, height);
        capture.set(org.opencv.videoio.Videoio.CAP_PROP_FPS, 100);
        // 测试是否可以捕获成功
        Mat testFrame = new Mat();
        boolean result = capture.read(testFrame);
        testFrame.release();
        return result;
    }

    /**
     * 将 OpenCV 图像帧（Mat 类型）转换为 JPEG 格式的字节数组
     * @param frame 当前的图像帧
     * @return 转换后的字节数组
     */
    private byte[] encodeFrame(Mat frame) {
        MatOfByte matOfByte = new MatOfByte();
        Imgcodecs.imencode(".jpg", frame, matOfByte);
        return matOfByte.toArray();
    }

    /**
     * 根据请求的质量调整帧分辨率
     * @param frame 当前的帧
     * @param quality 请求的视频质量
     */
    private Mat adjustFrameResolution(Mat frame, String quality) {
        int width = frame.width();
        int height = frame.height();

        switch (quality.toLowerCase()) {

            case "ultra_low": // 极低质量
                frame = resizeFrame(frame, width / 6, height / 6);  // 缩小至原始的八分之一
                break;
            case "low": // 低质量
                frame = resizeFrame(frame, width / 4, height / 4);  // 缩小至原始的四分之一
                break;
            case "medium": // 中等质量
                frame = resizeFrame(frame, width / 2, height / 2);  // 缩小至原始的一半
                break;
            case "high": // 高质量
                // 不改变分辨率
                break;
            case "original": // 原画
                // 使用原分辨率
                break;
            default:
                frame = resizeFrame(frame, width / 2, height / 2);  // 默认中等质量
                break;
        }
        return frame;
    }

    /**
     * 根据给定的宽度和高度调整帧分辨率
     * @param frame 当前的帧
     * @param width 新的宽度
     * @param height 新的高度
     * @return 调整后的帧
     */
    private Mat resizeFrame(Mat frame, int width, int height) {
        Mat resizedFrame = new Mat();
        org.opencv.imgproc.Imgproc.resize(frame, resizedFrame, new org.opencv.core.Size(width, height));
        return resizedFrame;
    }

    /**
     * 管理帧缓冲区，确保缓冲区不会超过设定大小
     * 如果缓冲区已满，移除最旧的帧
     * @param frameData 当前图像的字节数组
     */
    private synchronized void manageFrameBuffer(byte[] frameData) {
        if (frameBuffer.size() * frameData.length > BUFFER_SIZE) {
            frameBuffer.poll(); // 移除最旧的帧
        }
        frameBuffer.offer(frameData); // 加入新帧
    }

    /**
     * 将帧数据写入 HTTP 响应流
     * @param out HTTP 响应的输出流
     * @throws IOException 当写入过程中发生 I/O 错误时抛出异常
     */
    private void writeFrameToResponse(OutputStream out) {
        try {
            byte[] frame = frameBuffer.poll();  // 从缓冲区获取帧
            if (frame == null) {
                System.err.println("帧为空，跳过写入！");
                return;
            }

            // 写入响应流
            out.write("--frame\r\n".getBytes());
            out.write("Content-Type: image/jpeg\r\n\r\n".getBytes());
            out.write(frame);
            out.write("\r\n".getBytes());
            out.flush();
        } catch (IOException | NullPointerException e) {
            System.err.println("写入响应流时出现错误，可能是客户端断开连接：" + e.getMessage());
        }
    }

    /**
     * 检查客户端连接的心跳，若没有响应则主动断开客户端
     */
    private void checkClientHeartbeat(ClientSession clientSession) {
        try {
            // 进行简单的心跳检测（尝试发送无内容响应或检查连接）
            if (clientSession.isActive()) {
                // 如果客户端处于活跃状态，继续发送数据或做其他处理
                return;
            }

            // 客户端如果没反应，则从活跃会话列表中移除
            synchronized (lock) {
                if (activeSessions.containsKey(clientSession.getSessionId())) {
                    activeSessions.remove(clientSession.getSessionId());
                    activeClients.decrementAndGet(); // 降低活跃客户端计数
                    System.out.println("客户端 " + clientSession.getSessionId() + " 未响应，已断开");
                }
            }
        } catch (Exception e) {
            // 处理可能出现的异常，确保即使有异常也不会导致服务崩溃
            e.printStackTrace();
        }
    }

    /**
     * 清理工作：当没有活跃客户端时，释放摄像头资源
     */
    @PreDestroy
    public synchronized void cleanUp() {
        synchronized (lock) {
            if (activeClients.get() <= 0 && camera != null && camera.isOpened()) {
                camera.release(); // 释放摄像头资源
                camera = null;
                System.out.println("所有客户端已断开，摄像头已释放");
            }
        }
    }

    /**
     * 用于存储每个客户端连接的会话信息
     */
    private static class ClientSession {
        private final long sessionId;
        private final OutputStream outputStream;

        public ClientSession(long sessionId, OutputStream outputStream) {
            this.sessionId = sessionId;
            this.outputStream = outputStream;
        }

        public long getSessionId() {
            return sessionId;
        }

        /**
         * 检查客户端是否仍然活跃
         * 这里简单的策略是检查 outputStream 是否仍然可用或发送测试数据来确认
         */
        public boolean isActive() {
            try {
                // 执行简单的心跳操作，例如发送一个空的 HTTP 响应头部（此处可根据业务调整心跳机制）
                outputStream.write(0);
                return true;  // 如果写入成功，表示客户端连接正常
            } catch (IOException e) {
                return false;  // 如果发生异常，认为客户端已断开连接
            }
        }
    }
}
